/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pgentity;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import db.DBContext;
import db.PGKeys;
import db.RedisKey;
import db.mysql.DataRow;
import db.mysql.DataTable;
import db.mysql.DeleteExecutor;
import db.mysql.SQLBackgroundExecutor;
import db.mysql.SQLBatchStatement;
import db.mysql.SQLExecutor;
import db.mysql.SelectExecutor;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.pool2.impl.GenericObjectPool;
import share.PGHelper;
import share.PGMacro;
import share.StringBuilderPoolFactory;

/**
 *
 * @author Salm
 */
class Mailbox2 {
    private static final GenericObjectPool<StringBuilder> SB_MAIL_RENDERER = StringBuilderPoolFactory.makePool();
    private static final String MAIL_TABLE = "MAILS2";
    private static final Mailbox2 EMPTY_MAILBOX;
    private static final LoadingCache<String, Mailbox2> mailCache;
    static
    {
        mailCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(1000)
                .build(new Mailbox2Loader());
        
        EMPTY_MAILBOX = new Mailbox2("", Collections.EMPTY_LIST, 0);
    }
    
    private final String uid;
    private final RedisKey redisKey;
    private final List<Mail2> mails;
    private int unreadMail;
    
    private Mailbox2(String uid, List<Mail2> mails, int nUnread) {
        this.uid = uid;
        this.redisKey = key(uid);
        this.mails = mails;
        this.unreadMail = nUnread;
    }
    
    public static int getUnreadMails(String uid)
    {
        Mailbox2 mails2 = mailCache.getIfPresent(uid);
        if (mails2 != null)
            return mails2.getUnreadMail();
        
        return PGHelper.toInt(DBContext.Redis().get(key(uid)));
    }
    
    public static Mailbox2 getMails(String uid)
    {
        try
        {
            return mailCache.get(uid);
        } catch (Exception ex) {
            return EMPTY_MAILBOX;
        }
    }
    
    public static RedisKey key(String uid)
    {
        return PGKeys.USERS.getChild(uid).getChild(PGKeys.FD_UNREAD_MAILS);
    }
    
    public static void destroy(String uid)
    {
        DBContext.Redis().del(key(uid));
        DBContext.SQL().execute(DeleteExecutor.fromDesc(
                MAIL_TABLE,
                String.format("receiver = %s", uid)));
        
        mailCache.invalidate(uid);
    }
    
    public static void rotate(final String uid, final int limit)
    {
        try
        {
            DBContext.SQL().execute(new SQLExecutor() {
                @Override
                public Object exec(Statement stm) throws SQLException {
                    return stm.execute(String.format("CALL REMOVE_OLD_MAILS2(%s, %d)", uid, limit));
                }
            });

            mailCache.invalidate(uid);
        }
        catch (Exception ex)
        {
            // do nothing
        }
    }
    
    public static void sendMail(final String uid, final String sender,
            final Mail2.MailType type,
            final int iPar1, final int iPar2,
            final String tntPar1, final String tntPar2,
            final String txtPar1, final String txtPar2, final long now)
    {
        DBContext.SQL().executeBackground(new SQLBackgroundExecutor() {
            @Override
            public String queryToken() {
                return "INSERT INTO MAILS2 (type, sender, receiver, time, iPar1, iPar2, tntPar1, tntPar2, txtPar1, txtPar2) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
            }

            @Override
            public int batchSizeLimit() {
                return 1000;
            }

            @Override
            public void prepare(SQLBatchStatement.Manager batch) {
                SQLBatchStatement stm = batch.createStatement();
                stm.setInt(1, type.getID());
                stm.setInt(2, PGHelper.toInt(sender));
                stm.setInt(3, PGHelper.toInt(uid));
                stm.setLong(4, now);
                stm.setInt(5, iPar1);
                stm.setInt(6, iPar2);
                stm.setString(7, tntPar1);
                stm.setString(8, tntPar2);
                stm.setString(9, txtPar1);
                stm.setString(10, txtPar2);
            }
        });
        
        DBContext.Redis().incr(key(uid));
    }
    
    public static void sendMail(final String uid, final Mail2 mail)
    {
        
    }
    
    public Map<String, Object> buildAMF(int off, int len)
    {
        StringBuilder sb = null;
        try
        {
            sb = SB_MAIL_RENDERER.borrowObject();
            
            off = validOffset(off);
            len = validLen(off, len);
            Map<String, Object> data = new LinkedHashMap(len);

            for (int i = 0; i < len; ++i)
            {
                sb.setLength(0);
                sb.ensureCapacity(0x400);
                int index = off - i - 1;
                data.put(String.valueOf(index), mails.get(index).buildAMF(sb));
            }
            
            return data;
        }
        catch (Exception ex)
        {
            return Collections.EMPTY_MAP;
        }
        finally
        {
            if (sb != null)
            {
                SB_MAIL_RENDERER.returnObject(sb);
            }
        }
    }
    
    public int getUnreadMail()
    {
        return this.unreadMail;
    }
    
    public void resetUnreadMail()
    {
        if (this.unreadMail > 0)
        {
            DBContext.Redis().decrBy(redisKey, unreadMail);
        }
        
        this.unreadMail = 0;
    }
    
    private int validOffset(int off)
    {
        if (off < 0)
        {
            return mails.size() + off + 1;
        }
        
        return off;
    }
    
    private int validLen(int off, int len)
    {
        return Math.min(len, off);
    }
    
    private static class Mailbox2Loader extends CacheLoader<String, Mailbox2>
    {
        @Override
        public Mailbox2 load(String uid) throws Exception {
            DataTable dt = (DataTable) DBContext.SQL()
                    .execute(SelectExecutor.fromDesc(
                    MAIL_TABLE,
                    "`receiver` = " + uid + " ORDER BY `time` DESC LIMIT 0, 100",
                    PGMacro.MAIL_ID,
                    PGMacro.MAIL_TYPE,
                    PGMacro.MAIL_SENDER,
                    PGMacro.MAIL_SEND_TIME,
                    PGMacro.MAIL_IPAR1,
                    PGMacro.MAIL_IPAR2,
                    PGMacro.MAIL_TNTPAR1,
                    PGMacro.MAIL_TNTPAR2,
                    PGMacro.MAIL_TXTPAR1,
                    PGMacro.MAIL_TXTPAR2));
            
            return mailFromTable(uid, dt);
        };

        private Mailbox2 mailFromTable(String uid, DataTable dt) {
            List<Mail2> mails = new ArrayList(dt.size());
            for (DataRow row : dt) {
                mails.add(Mail2.fromDataRow(uid, row));
            }
            Collections.reverse(mails);
            
            int nUnread = PGHelper.toInt(DBContext.Redis().get(key(uid)));
            
            return new Mailbox2(uid, mails, nUnread);
        }
    }
}
